package com.progenies.ecg.asm31.model.codeparts;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import com.progenies.ecg.asm31.util.BytecodeInstructionsUtils;
import com.progenies.ecg.asm31.util.VariableRegister;
import com.progenies.ecg.model.AbstractCodeObject;
import com.progenies.ecg.model.ValueReference;
import com.progenies.ecg.model.codeparts.CaseBlock;
import com.progenies.ecg.model.codeparts.CodeBlock;
import com.progenies.ecg.model.codeparts.SwitchCodePart;

public class SwitchCodePartASM31 extends SwitchCodePart implements IConfigurableCodePart
{
	
	protected MethodVisitor visitor;
	protected AbstractCodeObject parent;
	protected VariableRegister varRegister;
	
	
	protected Label endLabel;
	
	
	public SwitchCodePartASM31(ValueReference<?> evaluatedValue, CaseBlock[] caseBlocks, CodeBlock defaultBlock)
	{
		this.evaluatedValue=evaluatedValue;
		this.caseBlocks=new ArrayList<CaseBlock>();
		for(CaseBlock tmp : caseBlocks)
			this.caseBlocks.add(tmp);
		this.defaultCodeBlock=defaultBlock;
	}

	@Override
	public void generateBytecode()
	{
		//TODO: Use TABLESWITH if the keys are near
		
		ArrayList<CaseBlock> sortedCases=new ArrayList<CaseBlock>(this.caseBlocks);
		Collections.sort(sortedCases, new Comparator<CaseBlock>() {

			@Override
			public int compare(CaseBlock o1, CaseBlock o2) {
				return o1.getKey() - o2.getKey();
			}
		});
		
		//create Label for each case and populate keys
		Label caseLabels[]=new Label[this.caseBlocks.size()];
		int keys[]=new int[caseLabels.length];
		for(int i=0;i<caseLabels.length;i++)
		{
			caseLabels[i]=new Label();
			keys[i]=sortedCases.get(i).getKey();
		}
		
		//end label (target for breaks)
		endLabel=new Label();
		
		//default label
		Label defaultLabel=new Label();
		
		
		//insert value to be evaluated into stack
		BytecodeInstructionsUtils.insertValueReferenceIntoStack(visitor, evaluatedValue, varRegister, this);
		
		//create switch code block
		visitor.visitLookupSwitchInsn(defaultLabel, keys, caseLabels);
		
		//visit each label and codeBlock
		for(int i=0;i<caseLabels.length;i++)
		{
			//get the correct label for the block (was created sorted)
			visitor.visitLabel(getLabel(caseLabels, keys, this.caseBlocks.get(i).getKey()));
			this.caseBlocks.get(i).getBodyBlock().generateBytecode();
			
			//the code blocks are generated in the same order than the users wrote them, so
			//there is no need to jump to other cases (for non-breaking cases)
		}
		
		//default case
		visitor.visitLabel(defaultLabel);
		this.defaultCodeBlock.generateBytecode();
		
		//end label
		visitor.visitLabel(endLabel);

	}

	private Label getLabel(Label[] caseLabels, int[] keys, int key) {
		for(int i=0;i<keys.length;i++)
		{
			if(keys[i]==key)
				return caseLabels[i];
		}
		throw new RuntimeException("Key should have been found! Please, contact developer");
	}

	public Label getEndLabel() {
		return this.endLabel;
	}

	

	@Override
	public void configure(MethodVisitor visitor, AbstractCodeObject parent,	VariableRegister varRegister) {
		this.visitor=visitor;
		this.parent=parent;
		this.varRegister=varRegister;
		
		if(this.defaultCodeBlock!=null && this.defaultCodeBlock instanceof IConfigurableCodePart)
			((IConfigurableCodePart)this.defaultCodeBlock).configure(visitor, this, varRegister);
		
		for(CaseBlock caseBlock : this.caseBlocks)
		{
			if(caseBlock!=null && caseBlock.getBodyBlock()!=null && caseBlock.getBodyBlock() instanceof IConfigurableCodePart)
				((IConfigurableCodePart)caseBlock.getBodyBlock()).configure(visitor, this, varRegister);
		}
	}

	@Override
	public boolean isReturnCodePart() {
		return false;
	}

	

}
